from game_state import GameState
from game_shared import WATER_CELLS, GAME_WINS_NEEDED
import copy

#=======================================================================================================================
# Game Class
#
# Game objects will hold the information for each game and are to be stored inside of GameStorage.
#=======================================================================================================================
class Game(object):
    def __init__(self, game_id):
        self.game_id = game_id
        self.player_1 = None
        self.player_2 = None
        self.state = GameState()
        self.winner = None
        self.loser = None
        self.invalid_shot_occurred = False
            
    def _distribute_win_loss(self, loser=None):
        if loser == 1:
            self.player_1.losses += 1
            self.player_2.wins += 1
            
        elif loser == 2:
            self.player_2.losses += 1
            self.player_1.wins += 1
            
        elif self.player_1.shots_taken > self.player_2.shots_taken:
            self.player_1.losses += 1
            self.player_2.wins += 1
            
        else:
            self.player_2.losses += 1
            self.player_1.wins += 1
    
    def set_player(self, player_id):
        if not self.player_1:
            self.player_1 = Player( player_id )
            self.state.next_state()
            self.state.current_state.tick_me()
            return
        
        elif not self.player_2:
            self.player_2 = Player( player_id )
            self.state.current_state.tick_me()
            self.state.next_state()
            return
        
        else:
            print "Two players already in game."
            raise
    
    def get_current_state(self):
        return self.state.current_state
    
    def place_boat(self, which_player, length, x_coord, y_coord, orientation):
        if which_player == 1: 
            self.player_1.place_boat( length, x_coord, y_coord, orientation )
            
            if self.player_1.all_boats_placed() and not self.state.current_state.tick: 
                self.state.current_state.tick_me()
                return
                
            elif self.player_1.all_boats_placed() and self.state.current_state.tick:
                self.state.current_state.tick_me()
                self.state.next_state()
                self.player_1.last_game_board = self.player_1.board
                self.player_2.last_game_board = self.player_2.board
                return
            
            return

            
        elif which_player == 2: 
            self.player_2.place_boat( length, x_coord, y_coord, orientation )
            
            if self.player_2.all_boats_placed() and not self.state.current_state.tick: 
                self.state.current_state.tick_me()
                return
                
            elif self.player_2.all_boats_placed() and self.state.current_state.tick:
                self.state.current_state.tick_me()
                self.state.next_state()
                self.player_1.last_game_board = self.player_1.board
                self.player_2.last_game_board = self.player_2.board
                return

            return
            
        else: 
            raise
    
    def shoot(self, which_player, x_coord, y_coord):
        try:
            hit = False
            
            # Player 1 shoots at Player 2
            if which_player == 1:
                
                # First check if this is a valid boat placement.  If not, then this player will automatically lose.
                if self.state.current_state.tick and ( self.player_1.shots_taken > self.player_2.shots_taken):
                    hit = "stop"
                    self._distribute_win_loss()
                    self._reset()
                    self.state.current_state.tick_me()
                    self.state.next_state()
                
                    if self._winner_present():
                        self.state.end_game()
                        self.finalize()
                
                    return hit

                elif self.player_2.all_boats_sunk() and not self.state.current_state.tick: 
                    self.state.current_state.tick_me()
                    hit = "stop"
                    return hit
                
                elif self.state.current_state.tick:
                    hit = "stop"
                    self._distribute_win_loss(loser=2)
                    self._reset()
                    self.state.current_state.tick_me()
                    self.state.next_state()
                    return hit
            
                # Otherwise, take a shot!
                else:  
                    self.player_1.shots_taken += 1
                    self.player_1.add_shot_to_shot_distribution(x_coord, y_coord)
                    hit = self.player_2.shoot(x_coord, y_coord)
            
                return hit
                
            # Player 2 shoots at Player 1  
            elif which_player == 2:
                if self.state.current_state.tick and ( self.player_2.shots_taken > self.player_1.shots_taken):
                    hit = "stop"
                    self._distribute_win_loss()
                    self._reset()
                    self.state.current_state.tick_me()
                    self.state.next_state()
                
                    if self._winner_present():
                        self.state.end_game()
                        self.finalize()
                
                    return hit
            
                # If you've sunk all of your opponents boats but your opponent hasn't.
                elif self.player_1.all_boats_sunk() and not self.state.current_state.tick: 
                    self.state.current_state.tick_me()
                    hit = "stop"
                    return hit
                
                elif self.state.current_state.tick:
                    hit = "stop"
                    self._distribute_win_loss(loser=1)
                    self._reset()
                    self.state.current_state.tick_me()
                    self.state.next_state()
                    return hit
            
                else:                
                    self.player_2.shots_taken += 1
                    self.player_2.add_shot_to_shot_distribution(x_coord, y_coord)
                    hit = self.player_1.shoot(x_coord, y_coord)
            
                return hit
                
        except:
            # This exception means an error occured while shooting.  If so, catch it, and issue a loss to the loser.
            hit = "fail"
            self.state.current_state.tick_me()
            return hit
            

    def _reset(self):
        self.player_1.last_game_shot_distribution = self.player_1.shot_distribution
        self.player_2.last_game_shot_distribution = self.player_2.shot_distribution
        self.player_1.shot_distribution = []
        self.player_2.shot_distribution = []
        self.player_1.shots_taken_list.append( float(self.player_1.shots_taken) )
        self.player_2.shots_taken_list.append( float(self.player_1.shots_taken) )
        self.player_1.avg_shots_taken = sum(self.player_1.shots_taken_list) / len(self.player_1.shots_taken_list) 
        self.player_2.avg_shots_taken = sum(self.player_2.shots_taken_list) / len(self.player_2.shots_taken_list)
        self.player_1.shots_taken = 0.0
        self.player_2.shots_taken = 0.0
        self.player_1.board = Board()
        self.player_2.board = Board()
    
    def _winner_present(self):
        if self.player_1.wins == GAME_WINS_NEEDED or self.player_2.wins == GAME_WINS_NEEDED: 
            return True
        else:
            return False

    def finalize(self):
        if self.player_1.wins == GAME_WINS_NEEDED: 
            self.winner = self.player_1.id
            self.loser = self.player_2.id
            
        elif self.player_2.wins == GAME_WINS_NEEDED: 
            self.winner = self.player_2.id
            self.loser = self.player_1.id
    
    def dummy(self):
        pass  
        
#=======================================================================================================================
# Player Class
#=======================================================================================================================
class Player(object):
    def __init__(self, id):
        self.id = id
        self.shots_taken = 0.0
        self.avg_shots_taken = 0.0
        self.shots_taken_list = [0]
        self.wins = 0
        self.losses = 0
        self.board = Board()
        self.last_game_board = Board()
        self.shot_distribution = []
        self.last_game_shot_distribution = []
    
    def add_shot_to_shot_distribution(self, x_coord, y_coord):
        self.shot_distribution.append( (x_coord, y_coord))
    
    def all_boats_sunk(self):
        return self.board.abs()

    def place_boat(self, length, x_coord, y_coord, orientation):
        self.board.place_boat(length, x_coord, y_coord, orientation)
    
    def shoot(self, x_coord, y_coord):
        return self.board.shoot( x_coord, y_coord )
    
    def all_boats_placed(self):
        return self.board.all_boats_placed()
    
    def dummy(self):
        pass

#=======================================================================================================================
# Board Class
#=======================================================================================================================
class Board(object):
    def __init__(self):
        self.boat_3a = None
        self.boat_3b = None
        self.boat_4a = None
        self.boat_4b = None
        self.boat_5 = None
        self.water_cells = copy.copy( WATER_CELLS )
        self.boat_cells = []
    
    def abs(self):
        ''' All Boats Sunk? ''' 
        boats = self._boats_on_board()
        boats = [ boat for boat in boats if boat ]
    
        return len(boats) == 0
        
    def place_boat(self, length, x_coord, y_coord, orientation):
        l = length
        
        try:
            if l == 3 and not self.boat_3a: 
                boat_cells = self.manage_cells(l, x_coord, y_coord, orientation)
                self.boat_3a = Boat(l, x_coord, y_coord, orientation, boat_cells)
                self.boat_3a.placed = True
                return
                
            elif l == 3 and not self.boat_3b: 
                boat_cells = self.manage_cells(l, x_coord, y_coord, orientation)
                self.boat_3b = Boat(l, x_coord, y_coord, orientation, boat_cells)
                self.boat_3b.placed = True
                return
                
            elif l == 4 and not self.boat_4a: 
                boat_cells = self.manage_cells(l, x_coord, y_coord, orientation)
                self.boat_4a = Boat(l, x_coord, y_coord, orientation, boat_cells)  
                self.boat_4a.placed = True
                return
                
            elif l == 4 and not self.boat_4b: 
                boat_cells = self.manage_cells(l, x_coord, y_coord, orientation)
                self.boat_4b = Boat(l, x_coord, y_coord, orientation, boat_cells)
                self.boat_4b.placed = True
                return
                
            elif l == 5 and not self.boat_5:
                boat_cells = self.manage_cells(l, x_coord, y_coord, orientation) 
                self.boat_5 = Boat(l, x_coord, y_coord, orientation, boat_cells)
                self.boat_5.placed = True
                return
            
            else:
                raise

        except:
            print"Error while trying to place boat."
            raise
 
    def shoot(self, x_coord, y_coord):
        coord = ( x_coord, y_coord )
        
        boats = self._boats_on_board()
        boats = [ boat for boat in boats if boat ]
        
        for boat in boats:
            if boat.hit( coord ): return True
        
        return False

    def manage_cells(self, length, x_coord, y_coord, orientation):
        ''' Raises an error if unable to manage these cells '''
        if (x_coord, y_coord) not in self.water_cells: raise
        
        # Find out which cells this boat wants to occupy. 
        # Raise an error if those cells are used or don't exist on the board
        potential_cells = []
        all_cells_valid = True
        
        for l in range(length):
            if orientation == 'h':
                potential_cell = (x_coord + l, y_coord)
            elif orientation == 'v':
                potential_cell = (x_coord, y_coord + l) 
            
            all_cells_valid = potential_cell in self.water_cells
            if not all_cells_valid: 
                print "[Error] Not all cells valid."
                raise 
            potential_cells.append(potential_cell)
        
        cells = potential_cells
        for cell in cells:
            self.boat_cells.append( cell )
            self.water_cells.remove( cell )
        
        return cells
    
    def _boats_on_board(self):
        return [    self.boat_3a,
                    self.boat_3b,
                    self.boat_4a,
                    self.boat_4b,
                    self.boat_5      ]
 
    def which_boats_floating(self):
        return [ boat for boat in self._boats_on_board() if boat.floating == True ]
    
    def all_boats_placed(self):
        boats = self._boats_on_board()
        boats = [ boat for boat in boats if boat ]
        return len(boats) == 5

    
#=======================================================================================================================
# Boat Class
#=======================================================================================================================
class Boat(object):
    def __init__(self, length, x_coord, y_coord, orientation, cells):
        self.origin = (x_coord, y_coord)
        self.orientation = orientation
        self.length = length
        self.cells = cells
        self.length = len(cells)
        self.floating = True
        self.placed = False
    
    def hit(self, coord):
        ''' Try to remove the coordinate represented by the tuple. Raises an error if the coordinate doesn't exist within the cell''' 
        try:
            if coord not in WATER_CELLS:
                raise
        except:
            raise
        
        try:
            self.cells.remove(coord)
            
            if self.length != 0:
                self.length -= 1
                if self.length == 0: self.floating = False
            
            return True
            
        except:
            return False

            
    def __len__(self):
        return self.length
        
        
        
        
        
        
        
        
        